﻿
#include "MyVtkInteractorStyleZoom.h"

#include "vtkCallbackCommand.h"
#include "vtkCamera.h"
#include "vtkMath.h"
#include "vtkMatrix3x3.h"
#include "vtkObjectFactory.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkActorCollection.h"
#include "vtkActor.h"
#include "vtkMapper.h"
#include "vtkRenderingOpenGLConfigure.h"
#include "vtkCompositePolyDataMapper.h"
#include "vtkCompositePolyDataMapper2.h"
#include "vtkPixelBufferObject.h"
#include <vtkAutoInit.h>

#include <QDebug>
#include <QDateTime>
#include <math.h>

//------------------- MyVtkInteractorStyleZoom ------------------//
MyVtkInteractorStyleZoom::MyVtkInteractorStyleZoom()
{
    this->MotionFactor = 10.0;
}


//------------------------------------------------------------------------------
MyVtkInteractorStyleZoom::~MyVtkInteractorStyleZoom() = default;

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::OnMouseMove()
{
    auto  eventPosition = this->Interactor->GetEventPosition();
    int x = eventPosition[0];
    int y = eventPosition[1];

    switch( this->State )
    {
    case VTKIS_ENV_ROTATE:
        this->FindPokedRenderer(x, y);
        this->EnvironmentRotate();
        this->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
        break;

    case VTKIS_ROTATE:
        this->FindPokedRenderer(x, y);
        this->Rotate();
        this->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
        break;

    case VTKIS_PAN:
        this->FindPokedRenderer(x, y);
        this->Pan();
        this->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
        break;

    case VTKIS_DOLLY:
        this->FindPokedRenderer(x, y);
        this->Dolly();
        this->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
        break;

    case VTKIS_SPIN:
        this->FindPokedRenderer(x, y);
        this->Spin();
        this->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
        break;
    }
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::OnLeftButtonDown()
{
    auto  eventPosition = this->Interactor->GetEventPosition();
    this->FindPokedRenderer(eventPosition[0],
                            eventPosition[1]);

    if( this->CurrentRenderer == nullptr )
    {
        return;
    }

    this->GrabFocus(this->EventCallbackCommand);
    if( this->Interactor->GetShiftKey() )
    {
        if( this->Interactor->GetControlKey() )
        {
            this->StartDolly();
        }
        else
        {
            this->StartPan();
        }
    }
    else
    {
        if( this->Interactor->GetControlKey() )
        {
            this->StartSpin();
        }
        else
        {
            this->StartRotate();
        }
    }
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::OnLeftButtonUp()
{
    switch( this->State )
    {
    case VTKIS_DOLLY:
        this->EndDolly();
        break;

    case VTKIS_PAN:
        this->EndPan();
        break;

    case VTKIS_SPIN:
        this->EndSpin();
        break;

    case VTKIS_ROTATE:
        this->EndRotate();
        break;
    }

    if( this->Interactor )
    {
        this->ReleaseFocus();
    }
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::OnMiddleButtonDown()
{
    auto  eventPosition = this->Interactor->GetEventPosition();
    this->FindPokedRenderer(eventPosition[0],
                            eventPosition[1]);

    if( this->CurrentRenderer == nullptr )
    {
        return;
    }

    this->GrabFocus(this->EventCallbackCommand);
    this->StartPan();
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::OnMiddleButtonUp()
{
    switch( this->State )
    {
    case VTKIS_PAN:
        this->EndPan();
        if( this->Interactor )
        {
            this->ReleaseFocus();
        }
        break;
    }
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::OnRightButtonDown()
{
    auto  eventPosition = this->Interactor->GetEventPosition();
    this->FindPokedRenderer(eventPosition[0],
                            eventPosition[1]);

    if( this->CurrentRenderer == nullptr )
    {
        return;
    }

    this->GrabFocus(this->EventCallbackCommand);

    if( this->Interactor->GetShiftKey() )
    {
        this->StartEnvRotate();
    }
    else
    {
        this->StartDolly();
    }
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::OnRightButtonUp()
{
    switch( this->State )
    {
    case VTKIS_ENV_ROTATE:
        this->EndEnvRotate();
        break;

    case VTKIS_DOLLY:
        this->EndDolly();
        break;
    }

    if( this->Interactor )
    {
        this->ReleaseFocus();
    }
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::OnMouseWheelForward()
{
    auto  eventPosition = this->Interactor->GetEventPosition();
    this->FindPokedRenderer(eventPosition[0],
                            eventPosition[1]);

    if( this->CurrentRenderer == nullptr )
    {
        return;
    }

    this->GrabFocus(this->EventCallbackCommand);
    this->StartDolly();
    double factor = this->MotionFactor * 0.2 * this->MouseWheelMotionFactor;
    this->Dolly(pow(1.1, factor));
    this->EndDolly();
    this->ReleaseFocus();
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::OnMouseWheelBackward()
{
    auto  eventPosition = this->Interactor->GetEventPosition();
    this->FindPokedRenderer(eventPosition[0],
                            eventPosition[1]);

    if( this->CurrentRenderer == nullptr )
    {
        return;
    }

    this->GrabFocus(this->EventCallbackCommand);
    this->StartDolly();
    double factor = this->MotionFactor * -0.2 * this->MouseWheelMotionFactor;
    this->Dolly(pow(1.1, factor));
    this->EndDolly();
    this->ReleaseFocus();
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::Rotate()
{
    auto  curRenderer = CurrentRenderer;
    if( curRenderer == nullptr )
    {
        return;
    }

    //compute the motionfctor
    int tempMotionFactor = 10;

    auto curRenderWin = curRenderer->GetRenderWindow();
    auto eventPosition = Interactor->GetEventPosition();
    auto lastEventPosition = Interactor->GetLastEventPosition();

    int dx = eventPosition[0] - lastEventPosition[0];
    int dy = eventPosition[1] - lastEventPosition[1];

    const int* size = curRenderWin->GetSize();

    double delta_elevation = -20.0 / size[1];
    double delta_azimuth = -20.0 / size[0];

    double rxf = dx * delta_azimuth * tempMotionFactor;
    double ryf = dy * delta_elevation * tempMotionFactor;

    vtkCamera* camera = curRenderer->GetActiveCamera();
    camera->Azimuth(rxf);
    camera->Elevation(ryf);
    camera->OrthogonalizeViewUp();

    //render the scene
    {
        if( AutoAdjustCameraClippingRange )
        {
            curRenderer->ResetCameraClippingRange();
        }

        if( Interactor->GetLightFollowCamera() )
        {
            curRenderer->UpdateLightsGeometryToFollowCamera();
        }

        auto StartTime = QDateTime::currentMSecsSinceEpoch();

        curRenderWin->SetDesiredUpdateRate(30);
        curRenderWin->Render();

        auto EndTime = QDateTime::currentMSecsSinceEpoch();
        qDebug() << "Rotate time = " << EndTime - StartTime << " ms";
    }
}

void MyVtkInteractorStyleZoom::EndRotate()
{
    auto StartTime = QDateTime::currentMSecsSinceEpoch();

    auto  curRenderer = CurrentRenderer;
    if( curRenderer == nullptr )
    {
        return;
    }

    if( AutoAdjustCameraClippingRange )
    {
        curRenderer->ResetCameraClippingRange();
    }

    if( Interactor->GetLightFollowCamera() )
    {
        curRenderer->UpdateLightsGeometryToFollowCamera();
    }

    curRenderer->GetRenderWindow()->Render();
    auto EndTime = QDateTime::currentMSecsSinceEpoch();
    qDebug() << "End Rotate time = " << EndTime - StartTime << " ms";

    vtkInteractorStyle::EndRotate();
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::Spin()
{
    auto  curRenderer = CurrentRenderer;
    if( curRenderer == nullptr )
    {
        return;
    }

    auto eventPosition = Interactor->GetEventPosition();
    auto eventLastPosition = Interactor->GetLastEventPosition();
    vtkRenderWindowInteractor* Interactor = this->Interactor;

    double* center = curRenderer->GetCenter();

    double newAngle = vtkMath::DegreesFromRadians(atan2(eventPosition[1] - center[1],
                                                        eventPosition[0] - center[0]));

    double oldAngle = vtkMath::DegreesFromRadians(atan2(eventLastPosition[1] - center[1],
                                                        eventLastPosition[0] - center[0]));

    vtkCamera* camera = curRenderer->GetActiveCamera();
    camera->Roll(newAngle - oldAngle);
    camera->OrthogonalizeViewUp();

    Interactor->Render();
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::Pan()
{
    auto  curRenderer = CurrentRenderer;
    if( curRenderer == nullptr )
    {
        return;
    }

    auto eventPosition = Interactor->GetEventPosition();
    auto lastEventPosition = Interactor->GetLastEventPosition();
    double viewFocus[4], focalDepth, viewPoint[3];
    double newPickPoint[4], oldPickPoint[4], motionVector[3];

    // Calculate the focal depth since we'll be using it a lot

    vtkCamera* camera = curRenderer->GetActiveCamera();
    camera->GetFocalPoint(viewFocus);
    this->ComputeWorldToDisplay(viewFocus[0], viewFocus[1], viewFocus[2], viewFocus);
    focalDepth = viewFocus[2];

    this->ComputeDisplayToWorld(eventPosition[0],
                                eventPosition[1],
                                focalDepth, newPickPoint);

    // Has to recalc old mouse point since the viewport has moved,
    // so can't move it outside the loop

    this->ComputeDisplayToWorld(lastEventPosition[0],
                                lastEventPosition[1],
                                focalDepth, oldPickPoint);

    // Camera motion is reversed

    motionVector[0] = oldPickPoint[0] - newPickPoint[0];
    motionVector[1] = oldPickPoint[1] - newPickPoint[1];
    motionVector[2] = oldPickPoint[2] - newPickPoint[2];

    camera->GetFocalPoint(viewFocus);
    camera->GetPosition(viewPoint);
    camera->SetFocalPoint(motionVector[0] + viewFocus[0],
                          motionVector[1] + viewFocus[1],
                          motionVector[2] + viewFocus[2]);

    camera->SetPosition(motionVector[0] + viewPoint[0],
                        motionVector[1] + viewPoint[1],
                        motionVector[2] + viewPoint[2]);

    Interactor->GetRenderWindow()->Render();
}

void MyVtkInteractorStyleZoom::EndPan()
{
    auto  curRenderer = CurrentRenderer;
    if( curRenderer == nullptr )
    {
        return;
    }

    if( Interactor->GetLightFollowCamera() )
    {
        curRenderer->UpdateLightsGeometryToFollowCamera();
    }

    Interactor->GetRenderWindow()->Render();
    vtkInteractorStyle::EndPan();
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::Dolly()
{
    auto  curRenderer = CurrentRenderer;
    if( curRenderer == nullptr )
    {
        return;
    }

    double* center = curRenderer->GetCenter();
    int dy = Interactor->GetEventPosition()[1] - Interactor->GetLastEventPosition()[1];
    double dyf = this->MotionFactor * dy / center[1];
    this->Dolly(pow(1.1, dyf));
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::Dolly(double factor)
{
    auto  curRenderer = CurrentRenderer;
    if( curRenderer == nullptr )
    {
        return;
    }

    vtkCamera* camera = curRenderer->GetActiveCamera();
    if( camera->GetParallelProjection() )
    {
        camera->SetParallelScale(camera->GetParallelScale() / factor);
    }
    else
    {
        camera->Dolly(factor);
        if( AutoAdjustCameraClippingRange )
        {
            curRenderer->ResetCameraClippingRange();
        }
    }

    if( Interactor->GetLightFollowCamera() )
    {
        curRenderer->UpdateLightsGeometryToFollowCamera();
    }

    Interactor->GetRenderWindow()->Render();
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::EnvironmentRotate()
{
    auto  curRenderer = this->CurrentRenderer;

    if( curRenderer == nullptr )
    {
        return;
    }

    auto curRenderWin = curRenderer->GetRenderWindow();

    auto eventPosition = Interactor->GetEventPosition();
    auto lastEventPosition = Interactor->GetLastEventPosition();

    int dx = eventPosition[0] - lastEventPosition[0];
    int sizeX = curRenderWin->GetSize()[0];

    vtkNew<vtkMatrix3x3> mat;

    double* up = curRenderer->GetEnvironmentUp();
    double* right = curRenderer->GetEnvironmentRight();

    double front[3];
    vtkMath::Cross(right, up, front);
    for( short i = 0; i < 3; i++ )
    {
        mat->SetElement(i, 0, right[i]);
        mat->SetElement(i, 1, up[i]);
        mat->SetElement(i, 2, front[i]);
    }

    double angle = (dx / static_cast<double>(sizeX)) * this->MotionFactor;

    double c = std::cos(angle);
    double s = std::sin(angle);
    double t = 1.0 - c;

    vtkNew<vtkMatrix3x3> rot;

    rot->SetElement(0, 0, t * up[0] * up[0] + c);
    rot->SetElement(0, 1, t * up[0] * up[1] - up[2] * s);
    rot->SetElement(0, 2, t * up[0] * up[2] + up[1] * s);

    rot->SetElement(1, 0, t * up[0] * up[1] + up[2] * s);
    rot->SetElement(1, 1, t * up[1] * up[1] + c);
    rot->SetElement(1, 2, t * up[1] * up[2] - up[0] * s);

    rot->SetElement(2, 0, t * up[0] * up[2] - up[1] * s);
    rot->SetElement(2, 1, t * up[1] * up[2] + up[0] * s);
    rot->SetElement(2, 2, t * up[2] * up[2] + c);

    vtkMatrix3x3::Multiply3x3(rot, mat, mat);

    // update environment orientation
    this->CurrentRenderer->SetEnvironmentUp(mat->GetElement(0, 1),
                                            mat->GetElement(1, 1),
                                            mat->GetElement(2, 1));

    this->CurrentRenderer->SetEnvironmentRight(mat->GetElement(0, 0),
                                               mat->GetElement(1, 0),
                                               mat->GetElement(2, 0));

    curRenderWin->Render();
}

MyVtkInteractorStyleZoom* MyVtkInteractorStyleZoom::New()
{
    return new MyVtkInteractorStyleZoom();
}

//------------------------------------------------------------------------------
void MyVtkInteractorStyleZoom::PrintSelf(ostream& os, vtkIndent indent)
{
    this->Superclass::PrintSelf(os, indent);
    os << indent << "MotionFactor: " << this->MotionFactor << "\n";
}
